#include "BoundingBox.h"
#include "BoundingSphere.h"

#include <glm/mat4x4.hpp>

BoundingBox::BoundingBox()
{
	mMin = glm::vec3(FLT_MAX);
	mMax = glm::vec3(-FLT_MAX);
}

BoundingBox::BoundingBox(const glm::vec3& min, const glm::vec3& max)
{
	mMin = min;
	mMax = max;
}

BoundingBox::BoundingBox(const Rect& rect, const glm::vec3& center)
{
	mMin = center + glm::vec3(rect.GetPosition(), 0.0f);
	mMax = center + glm::vec3(rect.GetPosition().x + rect.GetSize().x, rect.GetPosition().y + rect.GetSize().y, 0.0f);
}

BoundingBox::BoundingBox(const BoundingBox& other)
{
	mMin = other.mMin;
	mMax = other.mMax;
}

BoundingBox::BoundingBox(const glm::vec3* points, uint32_t numPoints)
{
	SetFromPoints(points, numPoints);
}

BoundingBox::BoundingBox(BoundingBox&& other)
{
	mMin = other.mMin;
	mMax = other.mMax;
}

BoundingBox::~BoundingBox()
{
}

BoundingBox& BoundingBox::operator=(const BoundingBox& other)
{
	mMin = other.mMin;
	mMax = other.mMax;

	return *this;
}

BoundingBox& BoundingBox::operator=(BoundingBox&& other)
{
	mMin = other.mMin;
	mMax = other.mMax;

	return *this;
}

void BoundingBox::Set(const glm::vec3& min, const glm::vec3& max)
{
	mMin = min;
	mMax = max;
}

void BoundingBox::Set(const glm::vec3* points, uint32_t numPoints)
{
	mMin = glm::vec3(FLT_MAX);
	mMax = glm::vec3(-FLT_MAX);

	for (uint32_t i = 0; i < numPoints; i++)
	{
		mMin = glm::min(mMin, points[i]);
		mMax = glm::max(mMax, points[i]);
	}
}

void BoundingBox::SetFromPoints(const glm::vec3* points, uint32_t numPoints)
{
	mMin = glm::vec3(FLT_MAX);
	mMax = glm::vec3(-FLT_MAX);

	for (uint32_t i = 0; i < numPoints; i++)
	{
		mMin = glm::min(mMin, points[i]);
		mMax = glm::max(mMax, points[i]);
	}
}

void BoundingBox::SetFromPoints(const glm::vec3* points, uint32_t numPoints, const glm::mat4& transform)
{
	
	glm::vec3 min(FLT_MAX, FLT_MAX, FLT_MAX);
	glm::vec3 max(-FLT_MAX, -FLT_MAX, -FLT_MAX);

	for (uint32_t i = 0; i < numPoints; i++)
	{
		glm::vec3 transformed = transform * glm::vec4(points[i], 1.0f);

		min.x = glm::min(min.x, transformed.x);
		min.y = glm::min(min.y, transformed.y);
		min.z = glm::min(min.z, transformed.z);

		max.x = glm::max(max.x, transformed.x);
		max.y = glm::max(max.y, transformed.y);
		max.z = glm::max(max.z, transformed.z);
	}

	mMin = min;
	mMax = max;
}

void BoundingBox::SetFromTransformedAABB(const BoundingBox& aabb, const glm::mat4& transform)
{
	
	glm::vec3 min = aabb.mMin;
	glm::vec3 max = aabb.mMax;

	glm::vec3 minTransformed = transform * glm::vec4(min, 1.0f);
	glm::vec3 maxTransformed = transform * glm::vec4(max, 1.0f);

	mMin = glm::min(minTransformed, maxTransformed);
	mMax = glm::max(minTransformed, maxTransformed);
}

void BoundingBox::Translate(const glm::vec3& translation)
{
	
	mMin += translation;
	mMax += translation;
}

void BoundingBox::Translate(float x, float y, float z)
{
	
	Translate(glm::vec3(x, y, z));
}

void BoundingBox::Scale(const glm::vec3& scale)
{
	
	mMin *= scale;
	mMax *= scale;
}

void BoundingBox::Scale(float x, float y, float z)
{
	
	mMin.x *= x;
	mMin.y *= y;
	mMin.z *= z;

	mMax.x *= x;
	mMax.y *= y;
	mMax.z *= z;
}

void BoundingBox::Rotate(const glm::mat3& rotation)
{
	
	glm::vec3 center = Center();
	glm::vec3 extents = GetExtents();

	glm::vec3 rotatedExtents = glm::vec3(rotation * glm::vec4(extents, 1.0f));

	mMin = center - rotatedExtents;
	mMax = center + rotatedExtents;
}

void BoundingBox::Transform(const glm::mat4& transform)
{
	
	glm::vec3 newCenter = transform * glm::vec4(Center(), 1.0f);
	glm::vec3 oldEdge = Size() * 0.5f;
	glm::vec3 newEdge = glm::vec3(
		glm::abs(transform[0][0]) * oldEdge.x + glm::abs(transform[1][0]) * oldEdge.y + glm::abs(transform[2][0]) * oldEdge.z,
		glm::abs(transform[0][1]) * oldEdge.x + glm::abs(transform[1][1]) * oldEdge.y + glm::abs(transform[2][1]) * oldEdge.z,
		glm::abs(transform[0][2]) * oldEdge.x + glm::abs(transform[1][2]) * oldEdge.y + glm::abs(transform[2][2]) * oldEdge.z);

	mMin = newCenter - newEdge;
	mMax = newCenter + newEdge;
}

BoundingBox BoundingBox::Transformed(const glm::mat4& transform) const
{
	
	BoundingBox box(*this);
	box.Transform(transform);
	return box;
}

void BoundingBox::Merge(const BoundingBox& other)
{
	
	if (other.mMin.x < mMin.x)
		mMin.x = other.mMin.x;
	if (other.mMin.y < mMin.y)
		mMin.y = other.mMin.y;
	if (other.mMin.z < mMin.z)
		mMin.z = other.mMin.z;
	if (other.mMax.x > mMax.x)
		mMax.x = other.mMax.x;
	if (other.mMax.y > mMax.y)
		mMax.y = other.mMax.y;
	if (other.mMax.z > mMax.z)
		mMax.z = other.mMax.z;
}

void BoundingBox::Merge(const glm::vec3& point)
{
	
	if (point.x < mMin.x)
		mMin.x = point.x;
	if (point.y < mMin.y)
		mMin.y = point.y;
	if (point.z < mMin.z)
		mMin.z = point.z;
	if (point.x > mMax.x)
		mMax.x = point.x;
	if (point.y > mMax.y)
		mMax.y = point.y;
	if (point.z > mMax.z)
		mMax.z = point.z;
}

void BoundingBox::Merge(const BoundingBox& other, const glm::mat4& transform)
{
	
	Merge(other.Transformed(transform));

}
void BoundingBox::Merge(const glm::vec3& point, const glm::mat4& transform)
{
	
	glm::vec3 transformed = transform * glm::vec4(point, 1.0f);
	Merge(transformed);
}

void BoundingBox::Merge(const BoundingBox& other, const glm::mat3& transform)
{
	
	Merge(other.Transformed(glm::mat4(transform)));
}
void BoundingBox::Merge(const glm::vec3& point, const glm::mat3& transform)
{
	
	glm::vec3 transformed = transform * glm::vec4(point, 1.0f);
	Merge(transformed);
}

Intersection BoundingBox::IsInside(const glm::vec3& point) const
{
	
	if (point.x < mMin.x || point.x > mMax.x || point.y < mMin.y || point.y > mMax.y || point.z < mMin.z || point.z > mMax.z)
		return Intersection::Outside;
	else
		return Intersection::Inside;
}

Intersection BoundingBox::IsInside(const BoundingBox& box) const
{
	
	if (box.mMax.x < mMin.x || box.mMin.x > mMax.x || box.mMax.y < mMin.y || box.mMin.y > mMax.y || box.mMax.z < mMin.z || box.mMin.z > mMax.z)
		return Intersection::Outside;
	else if (box.mMin.x < mMin.x || box.mMax.x > mMax.x || box.mMin.y < mMin.y || box.mMax.y > mMax.y || box.mMin.z < mMin.z || box.mMax.z > mMax.z)
		return Intersection::Intersects;
	else
		return Intersection::Inside;
}

Intersection BoundingBox::IsInside(const BoundingSphere& sphere) const
{
	
	float distSquared = 0;
	float temp;
	const glm::vec3& center = sphere.GetCenter();

	if (center.x < mMin.x)
	{
		temp = center.x - mMin.x;
		distSquared += temp * temp;
	}
	else if (center.x > mMax.x)
	{
		temp = center.x - mMax.x;
		distSquared += temp * temp;
	}
	if (center.y < mMin.y)
	{
		temp = center.y - mMin.y;
		distSquared += temp * temp;
	}
	else if (center.y > mMax.y)
	{
		temp = center.y - mMax.y;
		distSquared += temp * temp;
	}
	if (center.z < mMin.z)
	{
		temp = center.z - mMin.z;
		distSquared += temp * temp;
	}
	else if (center.z > mMax.z)
	{
		temp = center.z - mMax.z;
		distSquared += temp * temp;
	}

	float radius = sphere.GetRadius();
	if (distSquared > radius * radius)
		return Intersection::Outside;
	else if (center.x - radius < mMin.x || center.x + radius > mMax.x || center.y - radius < mMin.y || center.y + radius > mMax.y || center.z - radius < mMin.z || center.z + radius > mMax.z)
		return Intersection::Intersects;
	else
		return Intersection::Inside;
}

bool BoundingBox::IsInsideFast(const BoundingBox& box) const
{
	
	if (box.mMax.x < mMin.x || box.mMin.x > mMax.x || box.mMax.y < mMin.y || box.mMin.y > mMax.y || box.mMax.z < mMin.z || box.mMin.z > mMax.z)
		return false;
	else
		return true;
}

glm::vec3 BoundingBox::Size() const
{
	return mMax - mMin;
}

glm::vec3 BoundingBox::Center() const
{
	return (mMax + mMin) * 0.5f;
}

glm::vec3 BoundingBox::Min() const
{
	return mMin;
}

glm::vec3 BoundingBox::Max() const
{
	return mMax;
}
